/*
 * Lfile.c
 * Part of the !Memphis distribution
 * (c) bdb/nas, 1991-3
 */

/* #define DEBUG */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "kernel.h"
#include "swis.h"
/* #include "_swis.h" */
#include "util.h"
#include "compress.h"

/* debug support */
#ifdef DEBUG
#define DEBUGF printf
#else
#define DEBUGF 1?(void)0:(void)printf
#endif

static int ftruncate(int fp,int len)
{ return _kernel_osargs(3,fp,len)==_kernel_ERROR?EOF:0;
}

static int read(int fd, void *buff, int nbytes)
{
  _kernel_swi_regs r;
  
  r.r[0] = 4;
  r.r[1] = fd;
  r.r[2] = (int) buff;
  r.r[3] = nbytes;
  if (_kernel_swi(XOS_Bit + OS_GBPB, &r, &r)) return EOF;
  fd = r.r[3];
  return nbytes-fd;
}

static int write(int fd, void *buff, int nbytes)
{
  _kernel_swi_regs r;

  r.r[0] = 2;
  r.r[1] = fd;
  r.r[2] = (int) buff;
  r.r[3] = nbytes;
  if (_kernel_swi(XOS_Bit + OS_GBPB, &r, &r)) return EOF;
  fd = r.r[3];
  return nbytes-fd;
}

static int readat(int fd, int offset, void *buff, int nbytes)
{
  _kernel_swi_regs r;

  r.r[0] = 3;
  r.r[1] = fd;
  r.r[2] = (int) buff;
  r.r[3] = nbytes;
  r.r[4] = offset;
  if (_kernel_swi(XOS_Bit + OS_GBPB, &r, &r)) return EOF;
  fd = r.r[3];
  return nbytes-fd;
}

static int writeat(int fd, int offset, void *buff, int nbytes)
{
  _kernel_swi_regs r;

  r.r[0] = 1;
  r.r[1] = fd;
  r.r[2] = (int) buff;
  r.r[3] = nbytes;
  r.r[4] = offset;
  if (_kernel_swi(XOS_Bit + OS_GBPB, &r, &r)) return EOF;
  fd = r.r[3];
  return nbytes-fd;
}

#define OREAD 0
#define OCREATE 1
#define OREADWRITE 2

static int open(char *name,int mode)
{
  _kernel_swi_regs r;

  if (mode<0 || mode>2) 
    return EOF;
  r.r[0] = 0x4f+mode*0x40;
  r.r[1] = (int) name;
  if (_kernel_swi(XOS_Bit + OS_Find, &r, &r)) return EOF;
  mode = r.r[0];
  return mode;
}

static int close(int fd)
{
  _kernel_swi_regs r;

  r.r[0] = 0;
  r.r[1] = fd;
  if (_kernel_swi(XOS_Bit + OS_Find, &r, &r)) return EOF;
  return 0;
}

#define L_SET 0
#define L_INCR 1
#define L_XTND 2

static int lseek(int fd,int offset,int whence)
{ switch (whence)
  { case L_SET:
      break;
    case L_INCR:
      whence = _kernel_osargs(0,fd,0);
      if (whence==_kernel_ERROR) return EOF;
      offset += whence;
      break;
    case L_XTND:
      whence = _kernel_osargs(2,fd,0);
      if (whence==_kernel_ERROR) return EOF;
      offset += whence;
      break;
    default:
      return EOF;
  }
  return _kernel_osargs(1,fd,offset)==_kernel_ERROR?EOF:offset;
}

static DEFERR(mb_memory,0,"Out of memory");
static DEFERR(mb_NotFound,0,"Lump file not found");
static DEFERR(mb_Read,0,"Lump file read failed");
static DEFERR(mb_Create,0,"Could not create Lump file");
static DEFERR(mb_Write,0,"Lump file write failed");

#include "Lfile.h"
#define PAGESIZE 32768  /* Lumps we compress data in */

struct extent { int offset, len; };     /* a len of 0 means this extent isn't used */

struct lhandle
{ int size;             /* Uncompressed size of the file */
  int numpages;         /* amount of memory currently allocated */
  struct extent *pages; /* where in file corresponds to each page */
  int changed:1;        /* Whether changed since opened */
  int fp;               /* os file handle */
};

/* The format of a Lfile is as follows
 * Each 32K page of data is compressed separately, and stored as an extent in the file
 * When the file is closed, the index of these is stored at the end of the file, followed
 * by its uncompressed length (which tells how many extents there are
 */

static char *pagebuf;
static char *compressbuf;
static int pbpage;
static LHANDLE pbhandle;
static int pbchanged;

static _kernel_oserror *putpage(LHANDLE h,int page)
{ int i;
  struct extent e;
  DEBUGF("putpage: %p %d...",h,page);
  DEBUGF("size %d\n", h->size);
  if (h->size < PAGESIZE)
    e.len = compress(pagebuf,h->size,compressbuf,PAGESIZE);
  else
    e.len = compress(pagebuf,PAGESIZE,compressbuf,PAGESIZE);
  DEBUGF("compress to %d,",e.len);
  h->changed = 1;
  e.offset = 0;
lp:
  for (i=0;i<h->numpages;i++)
    if (i!=page && ( e.offset>=h->pages[i].offset ? e.offset < h->pages[i].offset+h->pages[i].len :
                                                    h->pages[i].offset < e.offset+e.len ))
    { e.offset = h->pages[i].offset+h->pages[i].len;
      goto lp;
    }
  h->pages[page] = e;
  DEBUGF("offset=%x,",e.offset);
  if (writeat(h->fp,e.offset,compressbuf,e.len)!=e.len)
    return ERR(mb_Write);
  pbchanged = 0;
  DEBUGF("ok\n");
  return NULL;
}

static _kernel_oserror *getpage(LHANDLE h,int page)
{ _kernel_oserror *err;
  struct extent e;
  DEBUGF("getpage: %p %d...",h,page);
  if (pbhandle==h && pbpage==page)
  { DEBUGF("bufferd ok\n");
    return NULL;
  }
  if (pbhandle && pbchanged)
  { err = putpage(pbhandle,pbpage);
    if (err) return err;
  }
  e = h->pages[page];
  if (e.len)
  { if (readat(h->fp,e.offset,compressbuf,e.len)!=e.len)
      return ERR(mb_Read);
    DEBUGF("read offset %x+%d size %d,",e.offset,e.len,h->size);
    if (h->size == e.len)
      uncompress(compressbuf,e.len,pagebuf,e.len);
    else
      uncompress(compressbuf,e.len,pagebuf,PAGESIZE);
  }
  else
    memset(pagebuf,0,PAGESIZE);
  pbhandle = h;
  pbpage = page;
  pbchanged = 0;
  DEBUGF("ok\n");
  return NULL;
}

/* #undef DEBUGF
#define DEBUGF 1?(void)0:(void)printf
 */

_kernel_oserror *Linit(void)
{
  DEBUGF("Linit....");
  pagebuf = malloc(PAGESIZE);
  pbhandle = NULL;
  compressbuf = malloc(PAGESIZE);
  if (!pagebuf || !compressbuf)
    return ERR(mb_memory);
  DEBUGF("ok\n");
  return NULL;
}
_kernel_oserror *Lfinish(void)
{
  DEBUGF("Lfinish...");
  if (pbhandle && pbchanged)
    putpage(pbhandle,pbpage);
  free(pagebuf);
  free(compressbuf);
  DEBUGF("ok\n");
  return NULL;
}
_kernel_oserror *Lcreate(char *filename,int length)
{ int fp;
  struct extent e;
  int n;
  DEBUGF("Lcreate %s %d...",filename,length);
  fp = open(filename,OCREATE);
  if (fp==EOF)
    return ERR(mb_Create);
  n = (length + PAGESIZE-1)/PAGESIZE;
  e.offset = e.len = 0;
  for (;n--;)
    if (write(fp,&e,sizeof(struct extent))==EOF)
      goto erk;
  if (write(fp,&length,sizeof(int))==EOF)
    goto erk;
  close(fp);
  DEBUGF("ok\n");
  return NULL;
erk:
  close(fp);
  remove(filename);
  return ERR(mb_Write);
}

_kernel_oserror *Lopen(char *filename, LHANDLE *h)
{
  LHANDLE p=malloc(sizeof(struct lhandle));
  int k;
  DEBUGF("Lopen %s..",filename);
  if (!p)
    return ERR(mb_memory);
  p->fp = open(filename,OREADWRITE);
  if (p->fp==EOF)
    return ERR(mb_NotFound);
  k = sizeof(p->size);
  if (lseek(p->fp,-k,L_XTND)==EOF ||
      read(p->fp,&p->size,k)!=k)
    return ERR(mb_Read);
  p->numpages = (p->size + PAGESIZE-1)/PAGESIZE;
  DEBUGF("size %d, pages=%d..",p->size,p->numpages);
  k = sizeof(struct extent)*p->numpages;
  p->pages = malloc(k);
  p->changed = 0;
  if (!p->pages)
  {
    close(p->fp);
    free(p);
    return ERR(mb_memory);
  }
  if (lseek(p->fp,-sizeof(p->size)-k,L_XTND)==EOF ||
      read(p->fp,p->pages,k)!=k)
    return ERR(mb_Read);
  *h=p;
  DEBUGF("=%p ok\n",p);
  return NULL;
}

_kernel_oserror *Lclose(LHANDLE h)
{ _kernel_oserror *err=NULL;
  int i,m,k;
  m = 0;
  DEBUGF("Lclose %p...",h);
  if (pbhandle==h && pbchanged)
    putpage(h,pbpage);
  if (h->changed)
  {
    for (i=0;i<h->numpages;i++)
      if (m<h->pages[i].offset+h->pages[i].len)
        m = h->pages[i].offset+h->pages[i].len;
    k = sizeof(struct extent)*h->numpages;
    if (writeat(h->fp,m,h->pages,k)!=k ||
        write(h->fp,&h->size,sizeof(int))!=sizeof(int) ||
        ftruncate(h->fp,m+k+sizeof(int))==EOF)
      err = ERR(mb_Write);
  }
  close(h->fp);
  free(h->pages);
  free(h);
  if (pbhandle==h)
    pbhandle = NULL;
  DEBUGF("ok\n");
  return err;
}


_kernel_oserror *Lread(LHANDLE h,int offset,int len, char *ptr)
{ _kernel_oserror *err;
  int page,o,l;
  DEBUGF("Lread %p %x+%x %p..",h,offset,len,ptr);
lp:
  page = offset/PAGESIZE;
  o = offset % PAGESIZE;
  err = getpage(h,page);
  if (err) return err;
  l = PAGESIZE - o;
  if (l>len) l = len;
  memcpy(ptr,&pagebuf[o],l);
  if (l<len)
  { len -= l;
    offset += l;
    ptr = (char *)ptr + l;
    goto lp;
  }
  DEBUGF("ok\n");
  return NULL;
}

_kernel_oserror *Lwrite(LHANDLE h,int offset,int len, char *ptr)
{ _kernel_oserror *err;
  int page,o,l;
  DEBUGF("Lwrite %p %x+%x %p..",h,offset,len,ptr);
lp:
  page = offset/PAGESIZE;
  o = offset % PAGESIZE;
  err = getpage(h,page);
  if (err) return err;
  l = PAGESIZE - o;
  if (l>len) l = len;
  memcpy(&pagebuf[o],ptr,l);
  pbchanged = 1;
  if (l<len)
  { len -= l;
    offset += l;
    ptr = (char *)ptr + l;
    goto lp;
  }
  DEBUGF("ok\n");
  return NULL;
}

_kernel_oserror *Lsetlength(LHANDLE h,int len)
{ int newnumpages = (len + PAGESIZE-1)/PAGESIZE;
  int i;
  struct extent *newpages;
  DEBUGF("Lsetlength %p %x...",h,len);
  if (pbhandle==h && pbchanged)
    putpage(h,pbpage);
  if (pbhandle==h)
    pbhandle=NULL;
  h->changed = 1;
  if (h->numpages==newnumpages)
  { h->size = len;
    DEBUGF("pages same ok\n");
    return NULL;
  }
  newpages = malloc(sizeof(struct extent)*newnumpages);
  if (!newpages)
    return ERR(mb_memory);
  memcpy(newpages,h->pages,sizeof(struct extent)*newnumpages);
  for (i=h->numpages;i<newnumpages;i++)
    newpages[i].len = 0;
  free(h->pages);
  h->size = len;
  h->pages = newpages;
  h->numpages = newnumpages;
  DEBUGF("ok\n");
  return NULL;
}
